﻿/*
____
 L|ypps Web controls for ASP.NET

Copyright (c) 2009 Alessandro Zifiglio, http://www.Typps.com
EMail: alessandro@Typps.com

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
namespace Typps
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Configuration;
    using System.Data;
    using System.Drawing;
    using System.Drawing.Design;
    using System.Globalization;
    using System.IO;
    using System.Reflection;
    using System.Resources;
    using System.Text;
    using System.Threading;
    using System.Web;
    using System.Web.Hosting;
    using System.Web.Script.Serialization;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.HtmlControls;
    using System.Web.UI.WebControls;
    using System.Xml;

    [ToolboxBitmap(typeof(ResourceDummy), "Typps.Resources.Images.slidepane.bmp")]
    [ParseChildren(true)]
    [DefaultProperty("ToggleButtonImageUrl")]
    [Designer(typeof(Typps.SlidePaneControlDesigner))]
    [ToolboxData("<{0}:SlidePane runat=\"server\"></{0}:SlidePane>")]
    public class SlidePane : ControlBase
    {
        #region Fields

        static readonly object EventSlidePaneItemCommand = new object();

        ClientScriptManager cs;
        HtmlGenericControl _body = null;
        string[] _commonCssImageList = new string[] { "togglebutton.gif", "tristatebutton110x33.gif", "tristatebutton200x33.gif", "tristateclosebutton16x48.gif" };
        string _cssKey = "Typps.SlidePane";
        string _keyFormat = "SlidePane_{0}";
        ITemplate _paneTemplate = null;
        string _rootImageSkinFolder = "Typps.Resources.Skins.{0}.Images.SlidePane.{1}";
        string _rootSkinFolder = "Typps.Resources.Skins.{0}.{1}";
        string _rootTemplateSkinFolder = "Typps.Resources.Skins.{0}.Templates.{1}";
        string _styleSheetName = "Css.slidepane.css";
        private string _webResponseData = string.Empty;

        #endregion Fields

        #region Constructors

        public SlidePane()
            : base("div")
        {
        }

        #endregion Constructors

        #region Events

        [Category("Action"),
        Description("Raised when a CommandEvent occurs within  SlidePaneItem template.")]
        public event SlidePaneItemCommandEventHandler SlidePaneItemCommand
        {
            add
            {
                Events.AddHandler(EventSlidePaneItemCommand, value);
            }
            remove
            {
                Events.RemoveHandler(EventSlidePaneItemCommand, value);
            }
        }

        //Invoke delegates registered with the Click event.
        public event ColorSelectionEventHandler Toggled;

        #endregion Events

        #region Properties

        /// <summary>
        /// Gets/Sets a value indicating whether to apply special effects
        /// when collapsing and expanding the slidepane. True by default.
        /// To animate the frames manually, use EmbeddedEffectsOnShow and EmbeddedEffectsOnHide. 
        /// </summary>
        [DefaultValue(true),
        NotifyParentProperty(true),
        RefreshProperties(RefreshProperties.Repaint)]
        public bool EnableEffects
        {
            get
            {
                return (ViewState["EnableEffects"] == null) ?
                    true : (bool)ViewState["EnableEffects"];
            }
            set
            {
                ViewState["EnableEffects"] = value;
            }
        }

        /// <summary>
        /// Gets/Sets the initial state of the control. By default it is true, meaning expanded.
        /// </summary>
        [DefaultValue(true),
        NotifyParentProperty(true),
        RefreshProperties(RefreshProperties.Repaint)]
        public bool Expanded
        {
            get
            {
                return (ViewState["Expanded"] == null) ?
                    true : (bool)ViewState["Expanded"];
            }
            set
            {
                ViewState["Expanded"] = value;
            }
        }

        /// <summary>
        /// Gets/Sets css float on this control. By default the control is floated to the right. This property
        /// takes effect only if the ToggleButtonOrientation is vertical.
        /// </summary>
        [DefaultValue(typeof(CssFloatStyle), "Right"),
        NotifyParentProperty(true),
        RefreshProperties(RefreshProperties.Repaint)]
        public CssFloatStyle Float
        {
            get
            {
                return (ViewState["Float"] == null) ?
                    CssFloatStyle.Right : (CssFloatStyle)ViewState["Float"];
            }
            set
            {
                ViewState["Float"] = value;
            }
        }

        [DefaultValue(""),
        Category("ClientSide Events"),
        NotifyParentProperty(true),
        RefreshProperties(RefreshProperties.Repaint)]
        public string OnClientBeginWebRequest
        {
            get { return (ViewState["OnClientBeginWebRequest"] == null) ? string.Empty : (string)ViewState["OnClientBeginWebRequest"]; }
            set { ViewState["OnClientBeginWebRequest"] = value; }
        }

        [DefaultValue(""),
        Category("ClientSide Events"),
        NotifyParentProperty(true),
        RefreshProperties(RefreshProperties.Repaint)]
        public string OnClientEndWebRequest
        {
            get { return (ViewState["OnClientEndWebRequest"] == null) ? string.Empty : (string)ViewState["OnClientEndWebRequest"]; }
            set { ViewState["OnClientEndWebRequest"] = value; }
        }

        /// <summary>
        /// Fires on the client when the toggle button is clicked that is used to expand/collapse the control.
        /// </summary>
        [DefaultValue(""),
        Category("ClientSide Events"),
        NotifyParentProperty(true),
        RefreshProperties(RefreshProperties.Repaint)]
        public string OnClientSlideToggle
        {
            get { return (ViewState["OnClientSlideToggle"] == null) ? string.Empty : (string)ViewState["OnClientSlideToggle"]; }
            set { ViewState["OnClientSlideToggle"] = value; }
        }

        [PersistenceMode(PersistenceMode.InnerProperty),
        TemplateContainer(typeof(SlidePaneItem))]
        public ITemplate PaneTemplate
        {
            get
            {
                return _paneTemplate;
            }
            set
            {
                _paneTemplate = value;
            }
        }

        /// <summary>
        /// When true the control will try to resgister the css required by this control.
        /// The default is true. When this control is consumed by the HTML Editor, it's set to
        /// false unless an external stylesheet is referenced by the Embedded instance of this control.
        /// This property is useful for control developers and not for use in user code.
        /// </summary>
        [Browsable(false),
        DefaultValue(true)]
        public virtual bool RegisterCSS
        {
            get
            {
                return (bool)(ViewState["RegisterCSS"] ?? true);
            }
            set
            {
                ViewState["RegisterCSS"] = value;
            }
        }

        /// <summary>
        /// Gets or sets the direction in which the Toggle button is css floated. This is only applicable
        /// when ToggleButtonOrientation is vertical. Only Right/Left are supported. 
        /// </summary>
        [DefaultValue(typeof(ButtonDirection), "Left"),
        NotifyParentProperty(true),
        RefreshProperties(RefreshProperties.Repaint)]
        public ButtonDirection ToggleButtonFloat
        {
            get
            {
                return (ViewState["ToggleButtonFloat"] == null) ?
                ButtonDirection.Left : (ButtonDirection)ViewState["ToggleButtonFloat"];
            }
            set { ViewState["ToggleButtonFloat"] = value; }
        }

        /// <summary>
        /// Gets or sets the direction in which to render the SlidePane's toggle button. Defaults to Horizontal
        /// </summary>
        [DefaultValue(typeof(Orientation), "Horizontal"),
        NotifyParentProperty(true),
        RefreshProperties(RefreshProperties.Repaint)]
        public Orientation ToggleButtonOrientation
        {
            get
            {
                return (ViewState["ToggleButtonOrientation"] == null) ?
                Orientation.Horizontal : (Orientation)ViewState["ToggleButtonOrientation"];
            }
            set { ViewState["ToggleButtonOrientation"] = value; }
        }

        protected override string ClassName
        {
            get
            {
                string cn = (this.CssClass != string.Empty) ? this.CssClass : this.ClientID;
                return cn + " slidepane";
            }
        }

        protected override string CssKey
        {
            get { return _cssKey; }
        }

        protected override string KeyFormat
        {
            get { return _keyFormat; }
        }

        protected override bool RenderSharedStyles
        {
            get { return false; }
        }

        protected override bool RenderTreeViewStyles
        {
            get { return false; }
        }

        protected override string RootImageSkinFolder
        {
            get
            {
                string url = _rootImageSkinFolder;
                if (IsCustomSkin)
                    url = Helper.GetPhysicalPathByResourceNamespace(url, this);
                return url;
            }
        }

        protected override string RootSkinFolder
        {
            get
            {
                string url = _rootSkinFolder;
                if (IsCustomSkin)
                    url = Helper.GetPhysicalPathByResourceNamespace(url, this);
                return url;
            }
        }

        protected override string RootTemplateSkinFolder
        {
            get
            {
                string url = _rootTemplateSkinFolder;
                if (IsCustomSkin)
                    url = Helper.GetPhysicalPathByResourceNamespace(url, this);
                return url;
            }
        }

        protected override string StartupCssStyleIdentifier
        {
            get { return "sp"; }
        }

        protected override string StyleSheetName
        {
            get { return _styleSheetName; }
        }

        #endregion Properties

        #region Methods

        internal virtual void OnSlidePaneDataRequest(EventArgs e)
        {
            string templateName = string.Format(RootTemplateSkinFolder,
            SelectedSkin, "SlidePane.html");
            _webResponseData = this.GetTemplateData(templateName).Replace("controlID", ClassName);
        }

        internal virtual void OnToggled(ColorSelectionEventArgs e)
        {
            if (Toggled != null)
            {
                Toggled(this, e);
            }
        }

        internal virtual void SetClientSideEventsDescriptor(ref ScriptControlDescriptor descriptor)
        {
            Dictionary<string, string> dic = new Dictionary<string, string>();
            dic.Add("beginWebRequest", OnClientBeginWebRequest);
            dic.Add("endWebRequest", OnClientEndWebRequest);
            dic.Add("onSlideToggle", OnClientSlideToggle);
            foreach (string eventName in dic.Keys)
            {
                string val = dic[eventName];
                if (!string.IsNullOrEmpty(val))
                    descriptor.AddEvent(eventName, val);
            }
        }

        internal virtual void SetEnableEffectsDescriptor(ref  ScriptControlDescriptor descriptor)
        {
            descriptor.AddProperty("enableEffects", EnableEffects);
        }

        internal virtual void SetExpandedDescriptor(ref ScriptControlDescriptor descriptor)
        {
            descriptor.AddProperty("expanded", Expanded);
        }

        internal virtual void SetGlobalClassNameDescriptor(ref ScriptControlDescriptor descriptor)
        {
            descriptor.AddProperty("className", ClassName);
        }

        internal virtual void SetToggleButtonFloatDescriptor(ref  ScriptControlDescriptor descriptor)
        {
            descriptor.AddProperty("toggleButtonFloat", ToggleButtonFloat);
        }

        internal virtual void SetToggleButtonOrientationDescriptor(ref ScriptControlDescriptor descriptor)
        {
            descriptor.AddProperty("orientation", this.ToggleButtonOrientation);
        }

        protected override void AddAttributesToRender(HtmlTextWriter writer)
        {
            // override to disable
            if (this.ID != null)
                writer.AddAttribute(HtmlTextWriterAttribute.Id, this.ClientID);
            if (this.ClassName != string.Empty)
                writer.AddAttribute("class", ClassName);

            if (ToggleButtonOrientation == Orientation.Vertical)
            {
                if(Float!= CssFloatStyle.NotSet)
                    Style["float"] = Float.ToString().ToLower();
                else
                    Style["float"] = "right";
            }

            base.AddAttributesToRender(writer);
        }

        protected override void CreateChildControls()
        {
            if (!IsBrowserUpLevel) return;

            if (cs == null && Page != null)
                cs = Page.ClientScript;

            CreateSlidePane();
            CreatePaneTemplate();
        }

        protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors()
        {
            ScriptControlDescriptor descriptor = new ScriptControlDescriptor("Typps.SlidePane", this.ClientID);
            if (DesignMode)
                return new ScriptDescriptor[] { descriptor };
            SetGlobalClassNameDescriptor(ref descriptor);
            SetClientSideEventsDescriptor(ref descriptor);
            SetExpandedDescriptor(ref descriptor);
            SetToggleButtonOrientationDescriptor(ref descriptor);
            SetToggleButtonFloatDescriptor(ref descriptor);
            SetEnableEffectsDescriptor(ref descriptor);
            return new ScriptDescriptor[] { descriptor };
        }

        protected override IEnumerable<ScriptReference> GetScriptReferences()
        {
            if (cs == null) return null;
            ScriptReference reference1 = new ScriptReference();
            bool isDebugMode = SM.ScriptMode == ScriptMode.Debug;
            string script1 = !isDebugMode ? "ClientScripts.startup.min.js" : "ClientScripts.startup.js";
            string script2 = !isDebugMode ? "ClientScripts.spryeffects.min.js" : "ClientScripts.spryeffects.js";
            string script3 = !isDebugMode ? "ClientScripts.effects.min.js" : "ClientScripts.effects.js";
            string script4 = !isDebugMode ? "ClientScripts.slidepane.min.js" : "ClientScripts.slidepane.js";

            reference1.Path = cs.GetWebResourceUrl(typeof(ResourceDummy),
                Helper.GetManifestResourceName(script1,
                ManifestResourceNamesCache, isDebugMode));

            ScriptReference reference2 = new ScriptReference();
            reference2.Path = cs.GetWebResourceUrl(typeof(ResourceDummy),
                Helper.GetManifestResourceName(script2,
                ManifestResourceNamesCache, isDebugMode));

            ScriptReference reference3 = new ScriptReference();
            reference3.Path = cs.GetWebResourceUrl(typeof(ResourceDummy),
                Helper.GetManifestResourceName(script3,
                ManifestResourceNamesCache, isDebugMode));

            ScriptReference reference4 = new ScriptReference();
            reference4.Path = cs.GetWebResourceUrl(typeof(ResourceDummy),
                Helper.GetManifestResourceName(script4,
                ManifestResourceNamesCache, isDebugMode));

            return new ScriptReference[] { reference1, reference2, reference3, reference4 };
        }

        protected override void LoadViewState(object state)
        {
            if (state != null)
            {
                object[] states = (object[])state;
                base.LoadViewState(states[0]);
            }
        }

        protected override bool OnBubbleEvent(object source, EventArgs e)
        {
            bool handled = false;
            if (e is SlidePaneItemCommandEventArgs)
            {
                SlidePaneItemCommandEventArgs args = (SlidePaneItemCommandEventArgs)e;

                OnSlidePaneItemCommand(args);
                handled = true;
            }
            return handled;
        }

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            cs = Page.ClientScript;
            EnsureChildControls();
            if (!this.DesignMode)
            {
                EnsureScriptManager();
                RaiseCallbackEvent();
            }
        }

        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);
            if (DesignMode || IsCallback) return;

            if (SM == null)
                throw new HttpException("A ScriptManager control must exist on the current page.");

            SM.RegisterScriptControl(this);

            if (RegisterCSS)
                InitializeCSSRegistration();
        }

        protected virtual void OnSlidePaneItemCommand(SlidePaneItemCommandEventArgs e)
        {
            SlidePaneItemCommandEventHandler onSlidePaneItemHandler =
                (SlidePaneItemCommandEventHandler)Events[EventSlidePaneItemCommand];
            if (onSlidePaneItemHandler != null)
                onSlidePaneItemHandler(this, e);
        }

        protected override void OnUnload(EventArgs e)
        {
            base.OnUnload(e);
            if (DesignMode) return;
            if (IsCallback)
            {
                if (!string.IsNullOrEmpty(CallbackArgument))
                {
                    ResponseWrite(string.Format("{0}$callbackdata${1}$sep${2}",
                        CallbackArgument, this.ClientID, _webResponseData), "text/plain");
                }
            }
        }

        protected override string QueryStringData(CssFile cssFileToRegister)
        {
            bool formatStyleSheet = SM.ScriptMode == ScriptMode.Debug;
            StringBuilder queryStrings = new StringBuilder(6);
            queryStrings.AppendFormat("&ctl={0}", (int)ControlName.SlidePane);
            queryStrings.AppendFormat("&s={0}", (int)Skin);
            queryStrings.AppendFormat("&cs={0}", CustomSkin);
            queryStrings.AppendFormat("&csf={0}", CustomSkinFolder);
            queryStrings.AppendFormat("&cc={0}", ClassName);
            queryStrings.AppendFormat("&fcss={0}", formatStyleSheet);
            queryStrings.AppendFormat("&cssfile={0}", cssFileToRegister);

            return queryStrings.ToString();
        }

        protected override void Render(HtmlTextWriter writer)
        {
            if (IsCallback) return;
            if (base.DesignMode)
            {
                this.EnsureChildControls();
            }
            else
            {
                if (SM != null && SM.SupportsPartialRendering)
                    SM.RegisterScriptDescriptors(this);
            }
            base.Render(writer);
        }

        protected override void ResetCollectionsAndStyles()
        {
        }

        protected override object SaveViewState()
        {
            object[] states = new object[1];
            states[0] = base.SaveViewState();
            return states;
        }

        protected override void TrackViewState()
        {
            base.TrackViewState();
        }

        private void CreatePaneTemplate()
        {
            if (PaneTemplate != null)
            {
                SlidePaneItem spi = new SlidePaneItem();
                PaneTemplate.InstantiateIn(spi);
                HtmlGenericControl div = new HtmlGenericControl("div");
                div.Attributes.Add("class", "template");
                _body.Controls.Add(div);
                div.Controls.Add(spi);
            }
        }

        private void CreateSlidePane()
        {
            string direction = ToggleButtonOrientation.ToString().ToLower();
            _body = new HtmlGenericControl("div");
            _body.ID = "body";
            _body.Attributes.Add("class", string.Format("body {0}", direction));
            _body.Style["float"] = "left";
            string state="expanded";
            if (!Expanded)
                _body.Style["display"] = "none";
            if (ToggleButtonFloat == ButtonDirection.Right || !Expanded)
                state = "collapsed";
            HtmlAnchor toggleButton = new HtmlAnchor();
            toggleButton.ID = "togglebutton";
            toggleButton.HRef = "javascript:void(0);";
            toggleButton.Attributes.Add("class", string.Format("togglebutton {0} {1}", direction, state));

            if (ToggleButtonOrientation == Orientation.Horizontal)
            {
                this.Controls.Add(_body);

                HtmlGenericControl divLineBreaker = new HtmlGenericControl("div");
                divLineBreaker.Style["clear"] = "both";
                this.Controls.Add(divLineBreaker);

                this.Controls.Add(toggleButton);
            }
            else
            {
                toggleButton.Style["float"] = "left";
                if (ToggleButtonFloat == ButtonDirection.Right)
                {
                    this.Controls.Add(_body);
                    this.Controls.Add(toggleButton);
                }
                else
                {
                    this.Controls.Add(toggleButton);
                    this.Controls.Add(_body);
                }
            }
        }

        private void RaiseCallbackEvent()
        {
            if (string.IsNullOrEmpty(CallbackArgument))
                return;
            switch (CallbackArgument)
            {
                case "SlidePanelDataRequest":
                    OnSlidePaneDataRequest(new EventArgs());
                    break;
            }
        }

        #endregion Methods
    }
}